﻿using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using Quartz;
using Quartz.Impl;
using Quartz.Impl.AdoJobStore;
using Quartz.Impl.AdoJobStore.Common;
using Quartz.Logging;
using Quartz.Spi;
using Quartz.Util;
using Serilog;
using System;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using LogLevel = Microsoft.Extensions.Logging.LogLevel;

namespace OnlineEducation.Quartz
{
    /// <summary>
    /// 
    /// </summary>
    public interface IJobScheduler
    {
        IScheduler _scheduler { get; set; }

        void Start();
        Task RunJob<T>(string jobName, DateTime dateTime, IDictionary<string, object> map = null) where T : IJob;
        void Stop();
    }
    /// <summary>
    /// 
    /// </summary>
    /// <seealso cref="Quartz.Logging.ILogProvider" />
    internal class ConsoleLogProvider : ILogProvider
    {
        public Logger GetLogger(string name)
        {
            return (level, func, exception, parameters) =>
            {
                
                    if (func != null)
                    {
                        Console.WriteLine("[" + DateTime.Now.ToLongTimeString() + "] [" + level + "] " + func(), parameters);
                    }
                return true;
            };
        }

        public IDisposable OpenNestedContext(string message)
        {
            throw new NotImplementedException();
        }

        public IDisposable OpenMappedContext(string key, string value)
        {
            throw new NotImplementedException();
        }

        public IDisposable OpenMappedContext(string key, object value, bool destructure = false)
        {
            throw new NotImplementedException();
        }
    }
    /// <summary>
    /// 
    /// </summary>
    /// <seealso cref="OnlineEducation.Quartz.IJobScheduler" />
    public class JobScheduler : IJobScheduler
    {
        public IScheduler _scheduler { get; set; }

        private readonly Microsoft.Extensions.Logging.ILogger _logger;

        private readonly SemaphoreSlim _semaphoreSlim = new SemaphoreSlim(1, 1);


        public JobScheduler(IServiceProvider IocContainer, ILoggerFactory loggerFactory, IConfiguration Configuration)
        {


            NameValueCollection props = new NameValueCollection
                {
                    { "quartz.serializer.type", "json" },
                    //以下配置需要数据库表配合使用，表结构sql地址：https://github.com/quartznet/quartznet/tree/master/database/tables
                    { "quartz.jobStore.type","Quartz.Impl.AdoJobStore.JobStoreTX, Quartz"},
                    { "quartz.jobStore.driverDelegateType","Quartz.Impl.AdoJobStore.MySQLDelegate, Quartz"},
                    { "quartz.jobStore.tablePrefix","QRTZ_"},
                    { "quartz.jobStore.dataSource","myDS"},
                    { "quartz.dataSource.myDS.connectionString",Configuration.GetConnectionString("DefaultConnection")},//连接字符串
                    { "quartz.dataSource.myDS.provider","MySql"},
                    { "quartz.jobStore.useProperties","true"}

                };


            _logger = loggerFactory.CreateLogger<JobScheduler>();

            var schedulerFactory = new StdSchedulerFactory(props);

            _scheduler = schedulerFactory.GetScheduler().Result;

            _scheduler.JobFactory = new IOCJobFactory(IocContainer, _scheduler); ;
        }

        public void Start()
        {
            _logger.LogInformation("Schedule job started.");

            //LogProvider.SetCurrentLogProvider(new ConsoleLogProvider());

            _scheduler.Start().Wait();

        }

        public void Stop()
        {
            _logger.LogInformation("Schedule jobs stopped.");

            if (_scheduler == null)
            {
                return;
            }
            try
            {
                if (_scheduler.Shutdown(waitForJobsToComplete: true).Wait(30000))
                {
                    _scheduler = null;

                    _logger.LogInformation("Schedule jobs stopped in 30s.");
                }
                else
                {
                    _logger.LogInformation("Schedule jobs stopped within more than 30s ");
                }
            }
            catch (Exception ex)
            {
                _logger.LogError(ex.Message + " " + ex.ToString());
            }

        }

        /// <summary>
        /// Runs the job with simple trigger at given dateTime, 
        /// it will repeat one time when job was triggered after 10s,
        /// make sure the job was run successfully.
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="jobName">Name of the job.</param>
        /// <param name="dateTime">The date time.</param>
        /// <param name="map">The map.</param>
        public async Task RunJob<T>(string jobName, DateTime dateTime, IDictionary<string, object> map = null) where T : IJob
        {
            await _semaphoreSlim.WaitAsync();//queue threads, only one pass

            string jobKey_Name = "J_" + jobName;

            string jobKey_Group = "G__";

            _logger.LogInformation($"create a new job {jobKey_Name} with trigger.......");

            try
            {
                IJobDetail job = await _scheduler.GetJobDetail(new JobKey(jobKey_Name, jobKey_Group));

                if (job == null)
                {
                    //if job not exist ,then create it 
                    job = JobBuilder.Create<T>()
                                         .SetJobData(new JobDataMap(map ?? new Dictionary<string, object>()))
                                         .StoreDurably(true)// store the job detail without any triggers
                                         .RequestRecovery(true)//
                                         .WithIdentity(jobKey_Name, jobKey_Group)
                                         .Build();
                }
                // create a trigger
                ITrigger trigger = CreateSimpleTrigger(jobKey_Name, jobKey_Group, dateTime);

                // make a schedule with job and tigger, if exist ,then replace it.
                await _scheduler.ScheduleJob(job, new List<ITrigger>() { trigger }, true);
            }
            catch (Exception ex)
            {
                _logger.LogError(ex.Message, ex);
                throw;
            }
            _semaphoreSlim.Release();

        }

        private ITrigger CreateSimpleTrigger(string name, string group, DateTime dateTime)
        {
            return TriggerBuilder.Create()
              .WithIdentity("T_" + name, "T__")
              .StartAt(dateTime.ToUniversalTime())
              .WithSimpleSchedule(x =>
              {
                  x.WithIntervalInSeconds(10).WithRepeatCount(1).WithMisfireHandlingInstructionFireNow();
              })
              .ForJob(name, group)
              .Build();
        }


    }
    /// <summary>
    /// 
    /// </summary>
    public static class JobSchedulerConfiguration
    {
        public static void UseJobScheduler(this IApplicationBuilder app)
        {
            var jobScheduler = app.ApplicationServices.GetRequiredService<IJobScheduler>();

            var lifetime = app.ApplicationServices.GetRequiredService<IHostApplicationLifetime>();

            lifetime.ApplicationStarted.Register(jobScheduler.Start);

            lifetime.ApplicationStopped.Register(jobScheduler.Stop);
        }
    }

    /// <summary>
    /// IOCJobFactory ： 
    /// </summary>
    public class IOCJobFactory : IJobFactory
    {
        protected readonly IServiceProvider _container;
        protected readonly IScheduler _scheduler;

        public IOCJobFactory(IServiceProvider container, IScheduler scheduler)
        {
            _container = container;
            _scheduler = scheduler;
        }

        //Called by the scheduler at the time of the trigger firing, in order to produce
        //     a Quartz.IJob instance on which to call Execute.
        public IJob NewJob(TriggerFiredBundle bundle, IScheduler scheduler)
        {
            return _container.GetService(bundle.JobDetail.JobType) as IJob;
        }

        // Allows the job factory to destroy/cleanup the job if needed.
        public void ReturnJob(IJob job)
        {

        }



    }





}
